﻿'   Awesemulator.vb
'   WireBear Awesemulator
'
'   Created by Chris Kent
'   Copyright 2011 WireBear. All rights reserved
'
#Region "License Information"
'   WireBear Awesemulator is free software: you can redistribute it and/or modify
'   it under the terms of the GNU General Public License as published by
'   the Free Software Foundation, either version 3 of the License, or
'   (at your option) any later version.
'
'   WireBear Awesemulator is distributed in the hope that it will be useful,
'   but WITHOUT ANY WARRANTY; without even the implied warranty of
'   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
'   GNU General Public License for more details.
'
'   You should have received a copy of the GNU General Public License
'   along with WireBear Awesemulator - License.txt
'   If not, see <http://www.gnu.org/licenses/>.
#End Region


Imports WindowsInput

Public Class Awesemulator

    Private Const COUNTERPLACEHOLDER As String = "<#>"

    Private Config As Configuration

    Enum StatusBarState
        SiteLink
        ControlCommand
        Screenshot
    End Enum

    Private CurrentStatusBarState As StatusBarState = StatusBarState.SiteLink
    Private LastScreenshotPath As String = String.Empty

    Private myOverlay As New Overlay()
    Private myPointer As New CustomPointer()
    Private GlobalCursorVisible As Boolean = True

    'Global Hotkeys for custom actions
    Private WithEvents hkScreenshot As GlobalHotKeyReceiver
    Private WithEvents hkCursor As GlobalHotKeyReceiver
    Private WithEvents hkOverlay As GlobalHotKeyReceiver

    Private Sub Awesemulate_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
        LoadSettings()

        'Fill Image Formats
        cbFormat.Items.Add(Imaging.ImageFormat.Png)
        cbFormat.Items.Add(Imaging.ImageFormat.Jpeg)
        cbFormat.Items.Add(Imaging.ImageFormat.Gif)
        cbFormat.Items.Add(Imaging.ImageFormat.Bmp)
        If My.Settings.cbFormatSelectedIndex < cbFormat.Items.Count Then cbFormat.SelectedIndex = My.Settings.cbFormatSelectedIndex

        'Fill Cursor Scaling
        cbOverlayCursorScale.Items.Add(New Scaling() With {.Description = "Match", .WidthScale = 0, .HeightScale = 0})
        cbOverlayCursorScale.Items.Add(New Scaling() With {.Description = "100%", .WidthScale = 1.0, .HeightScale = 1.0})
        cbOverlayCursorScale.Items.Add(New Scaling() With {.Description = "75%", .WidthScale = 0.75, .HeightScale = 0.75})
        cbOverlayCursorScale.Items.Add(New Scaling() With {.Description = "66%", .WidthScale = 0.66, .HeightScale = 0.66})
        cbOverlayCursorScale.Items.Add(New Scaling() With {.Description = "50%", .WidthScale = 0.5, .HeightScale = 0.5})
        cbOverlayCursorScale.Items.Add(New Scaling() With {.Description = "33%", .WidthScale = 0.33, .HeightScale = 0.33})
        If My.Settings.cbOverlayCursorScaleSelectedIndex < cbOverlayCursorScale.Items.Count Then cbOverlayCursorScale.SelectedIndex = My.Settings.cbOverlayCursorScaleSelectedIndex

        'Setup Display
        KeepOnTopToolStripMenuItem.Checked = My.Settings.KeepOnTop
        AssumeLandscapeLeftToolStripMenuItem.Checked = My.Settings.LandscapeLeft

        'Configure Global Hooks (Windows key is used by emulator, so careful!)
        hkScreenshot = New GlobalHotKeyReceiver(Keys.Space, False, True, True, False)
        hkOverlay = New GlobalHotKeyReceiver(Keys.O, False, True, True, False)
        hkCursor = New GlobalHotKeyReceiver(Keys.C, False, True, True, False)
    End Sub

    Private Sub Awesemulator_FormClosed(ByVal sender As Object, ByVal e As FormClosedEventArgs) Handles Me.FormClosed
        'Ensure cursor is visible when program closes
        AdjustGlobalCursor(True)
    End Sub

    ''' <summary>
    ''' Sets up the Form using config files
    ''' </summary>
    Private Sub LoadSettings()
        Config = Configuration.FromFiles()

        'Clear all the dropdowns
        cbScreenshotSkin.Items.Clear()
        cbOverlaySkin.Items.Clear()
        cbOverlayCursor.Items.Clear()

        'Fill Skin Dropdowns (Start with default dummy entries, then load from settings)
        Dim blankSkin As New Skin() With {.IsDummySkin = True, .Name = "None", .Purpose = "MarketPlace"}
        cbScreenshotSkin.Items.Add(blankSkin)
        Dim blankOverlay As New Skin() With {.IsDummySkin = True, .Name = "None", .Purpose = "No Overlay"}
        cbOverlaySkin.Items.Add(blankOverlay)
        For Each f As Skin In Config.Skins
            If f.UseForScreenshot Then cbScreenshotSkin.Items.Add(f)
            If f.UseForOverlay Then cbOverlaySkin.Items.Add(f)
        Next
        If My.Settings.cbScreenshotSkinSelectedIndex < cbScreenshotSkin.Items.Count Then cbScreenshotSkin.SelectedIndex = My.Settings.cbScreenshotSkinSelectedIndex
        If My.Settings.cbOverlaySkinSelectedIndex < cbOverlaySkin.Items.Count Then cbOverlaySkin.SelectedIndex = My.Settings.cbOverlaySkinSelectedIndex

        'Fill Cursor Dropdown (Start with default dummy entry, then load from settings)
        Dim blankCursor As New CursorInfo() With {.IsDummyCursor = True, .Name = "Standard", .Purpose = "Pointer"}
        cbOverlayCursor.Items.Add(blankCursor)
        For Each c As CursorInfo In Config.Cursors
            cbOverlayCursor.Items.Add(c)
        Next
        If My.Settings.cbOverlayCursorSelectedIndex < cbOverlayCursor.Items.Count Then cbOverlayCursor.SelectedIndex = My.Settings.cbOverlayCursorSelectedIndex
    End Sub


#Region "Setting Save Events"

    'These settings are manually managed rather than have ApplicationSettings bindings for more
    'fine grain control - specifically for combobox selections to ensure a change in the config
    'files between runs doesn't cause an exception

    Private Sub KeepOnTopToolStripMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) Handles KeepOnTopToolStripMenuItem.Click
        My.Settings.KeepOnTop = KeepOnTopToolStripMenuItem.Checked
    End Sub

    Private Sub AssumeLandscapeLeftToolStripMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) Handles AssumeLandscapeLeftToolStripMenuItem.Click
        My.Settings.LandscapeLeft = AssumeLandscapeLeftToolStripMenuItem.Checked
    End Sub

    Private Sub cbFormat_SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs) Handles cbFormat.SelectedIndexChanged
        My.Settings.cbFormatSelectedIndex = cbFormat.SelectedIndex
    End Sub

    Private Sub cbScreenshotSkin_SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs) Handles cbScreenshotSkin.SelectedIndexChanged
        My.Settings.cbScreenshotSkinSelectedIndex = cbScreenshotSkin.SelectedIndex
    End Sub

    Private Sub cbOverlaySkin_SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs) Handles cbOverlaySkin.SelectedIndexChanged
        My.Settings.cbOverlaySkinSelectedIndex = cbOverlaySkin.SelectedIndex
    End Sub

    Private Sub cbOverlayCursor_SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs) Handles cbOverlayCursor.SelectedIndexChanged
        My.Settings.cbOverlayCursorSelectedIndex = cbOverlayCursor.SelectedIndex
    End Sub

    Private Sub cbOverlayCursorScale_SelectedIndexChanged(sender As Object, e As System.EventArgs) Handles cbOverlayCursorScale.SelectedIndexChanged
        My.Settings.cbOverlayCursorScaleSelectedIndex = cbOverlayCursorScale.SelectedIndex
    End Sub

    Private Sub AddStatsToSkinsToolStripMenuItem_Click(sender As System.Object, e As System.EventArgs) Handles AddStatsToSkinsToolStripMenuItem.Click
        My.Settings.AddStatsToSkins = AddStatsToSkinsToolStripMenuItem.Checked
    End Sub
#End Region

#Region "General Interface Events"

    ''' <summary>
    ''' Depending on the content type of the StatusStrip Label, double clicking will offer some handy
    ''' shortcuts (open the site link, open the last screenshot, or open the control guide)
    ''' </summary>
    Private Sub StatusStrip_DoubleClick(ByVal sender As Object, ByVal e As EventArgs) Handles StatusStrip.DoubleClick
        Select Case CurrentStatusBarState
            Case StatusBarState.SiteLink
                Process.Start("http://www.wirebear.com/blog")
            Case StatusBarState.Screenshot
                If Not String.IsNullOrEmpty(LastScreenshotPath) Then Process.Start(LastScreenshotPath)
            Case StatusBarState.ControlCommand
                'TODO: Add guide to Controls
        End Select
    End Sub

    ''' <summary>
    ''' Opens a Folder Browser dialog to select the location to save Screenshots too
    ''' </summary>
    Private Sub btnSaveToBrowse_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnSaveToBrowse.Click
        If FolderBrowserDialog.ShowDialog() = Windows.Forms.DialogResult.OK Then
            txtScreenshotDirectory.Text = FolderBrowserDialog.SelectedPath
        End If
    End Sub

    ''' <summary>
    ''' Opens whatever directory (if it exists) is set to store screenshots
    ''' </summary>
    Private Sub OpenDirectory_Click(ByVal sender As Object, ByVal e As EventArgs) Handles OpenDirectoryToolStripMenuItem.Click, btnSaveToOpenDirectory.Click
        If My.Computer.FileSystem.DirectoryExists(txtScreenshotDirectory.Text) Then
            Process.Start("explorer.exe", txtScreenshotDirectory.Text)
        Else
            MsgBox(String.Format("The Selected Directory cannot be found!{0}{0}{1}", Environment.NewLine, txtScreenshotDirectory.Text), MsgBoxStyle.Exclamation Or MsgBoxStyle.OkOnly, "Imaginary Folder is Imaginary And We are Sad")
        End If
    End Sub

    ''' <summary>
    ''' Adds the counter placeholder to the end of the screenshot filename
    ''' </summary>
    Private Sub btnInsertPlaceholder_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnInsertPlaceholder.Click
        txtScreenshotFilename.Text &= COUNTERPLACEHOLDER
    End Sub

    ''' <summary>
    ''' Although the counter can be set to 0, it defaults to 1 and the reset puts it back to 1
    ''' </summary>
    Private Sub ResetCounter(ByVal sender As Object, ByVal e As EventArgs) Handles btnCounterReset.Click, ResetCounterToolStripMenuItem.Click
        nudCounter.Value = 1
    End Sub

    ''' <summary>
    ''' Shows a version of the SplashScreen that closes on losing focus
    ''' </summary>
    Private Sub AboutToolStripMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) Handles AboutToolStripMenuItem.Click
        Dim about As New SplashScreen() With {.Closable = True}
        about.Show()
    End Sub

    ''' <summary>
    ''' Opens the Configuration folder
    ''' Helpful for on the fly modifications
    ''' </summary>
    Private Sub OpenSettingsxmlToolStripMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) Handles OpenConfigurationFolderToolStripMenuItem.Click
        If My.Computer.FileSystem.DirectoryExists(Configuration.CONFIGURATION_FOLDER) Then
            Process.Start("explorer.exe", Configuration.CONFIGURATION_FOLDER)
        Else
            MsgBox("The Configuration Directory cannot be found!", MsgBoxStyle.Exclamation Or MsgBoxStyle.OkOnly, "Uh oh!")
        End If
    End Sub

    ''' <summary>
    ''' Reloads all settings from the config files - helpful for on the fly changes
    ''' </summary>
    Private Sub ReloadSettingsToolStripMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) Handles ReloadSettingsToolStripMenuItem.Click
        LoadSettings()
    End Sub

    ''' <summary>
    ''' Opens Documentation Wiki
    ''' </summary>
    Private Sub InstructionsToolStripMenuItem_Click(sender As System.Object, e As System.EventArgs) Handles InstructionsToolStripMenuItem.Click
        Process.Start("http://awesemulator.codeplex.com/documentation")
    End Sub

#End Region

#Region "Control"

    'The controls take advantage of emulator shortcut keys. These keys are not obvious, so awesemulator
    'provides an easy reference/interface for applying them. To help educate the user, whenever a control
    'command is "sent" (pressed), the command and the key are shown in the status strip.

    ''' <summary>
    ''' Brings the Emulator to the foreground and presses a key
    ''' </summary>
    ''' <param name="key">The key to press</param>
    ''' <param name="CommandName">The name of the command (for display)</param>
    ''' <param name="KeyName">The display name of the key</param>
    Private Sub SendCommand(ByVal key As VirtualKeyCode, ByVal CommandName As String, ByVal KeyName As String)
        Try
            Dim hWnd As IntPtr = ScreenShot.ProcessHandle(Config.Emulator.ProcessName)
            ScreenShot.BringWindowToFront(hWnd)
            InputSimulator.SimulateKeyPress(key)
            lblStatus.Text = String.Format("{0} [{1}]", CommandName, KeyName)
            CurrentStatusBarState = StatusBarState.ControlCommand
        Catch ex As Exception
            MsgBox(ex.ToString())
        End Try
    End Sub

    Private Sub SendKeyboardEnable(ByVal sender As Object, ByVal e As EventArgs) Handles btnKeyboardEnable.Click, LocalKeyboardEnableToolStripMenuItem.Click
        SendCommand(VirtualKeyCode.PRIOR, "Local Keyboard Enabled", "Page Up")
    End Sub

    Private Sub SendKeyboardDisable(ByVal sender As Object, ByVal e As EventArgs) Handles btnKeyboardDisable.Click, LocalKeyboardDisableToolStripMenuItem.Click
        SendCommand(VirtualKeyCode.NEXT, "Local Keyboard Disabled", "Page Down")
    End Sub

    Private Sub SendVolumeUp(ByVal sender As Object, ByVal e As EventArgs) Handles btnVolumeUp.Click, VolumeUpToolStripMenuItem.Click
        SendCommand(VirtualKeyCode.F9, "Volume Increased", "F9")
    End Sub

    Private Sub SendVolumeDown(ByVal sender As Object, ByVal e As EventArgs) Handles btnVolumeDown.Click, VolumeDownToolStripMenuItem.Click
        SendCommand(VirtualKeyCode.F10, "Volume Decreased", "F10")
    End Sub

    Private Sub SendCameraHalf(ByVal sender As Object, ByVal e As EventArgs) Handles btnCameraHalf.Click, CameraHalfPressToolStripMenuItem.Click
        SendCommand(VirtualKeyCode.F6, "Camera Button Half Pressed (VS Pro Only)", "F6")
    End Sub

    Private Sub SendCameraFull(ByVal sender As Object, ByVal e As EventArgs) Handles btnCamera.Click, CameraFullPressToolStripMenuItem.Click
        SendCommand(VirtualKeyCode.F7, "Camera Button Full Pressed (VS Pro Only)", "F7")
    End Sub

    Private Sub SendPower(ByVal sender As Object, ByVal e As EventArgs) Handles btnPower.Click, PowerToolStripMenuItem.Click
        SendCommand(VirtualKeyCode.SNAPSHOT, "Power Button Pressed (VS Pro Only)", "Print Screen")
    End Sub

    Private Sub SendFunction(ByVal sender As Object, ByVal e As EventArgs) Handles btnFunction.Click, KeyboardFunctionToolStripMenuItem.Click
        SendCommand(VirtualKeyCode.LCONTROL, "Hardware Keyboard Function Pressed", "Left Ctrl")
    End Sub

    Private Sub SendSymbol(ByVal sender As Object, ByVal e As EventArgs) Handles btnSymbol.Click, KeyboardSymbolToolStripMenuItem.Click
        SendCommand(VirtualKeyCode.RCONTROL, "Hardware Keyboard Symbol Pressed", "Right Ctrl")
    End Sub

    Private Sub SendAccent(ByVal sender As Object, ByVal e As EventArgs) Handles btnAccent.Click, KeyboardAccentToolStripMenuItem.Click
        SendCommand(VirtualKeyCode.LMENU, "Hardware Keyboard Accent Pressed", "Left Alt")
    End Sub

    Private Sub SendEmoticon(ByVal sender As Object, ByVal e As EventArgs) Handles btnEmoticon.Click, KeyboardEmoticonToolStripMenuItem.Click
        SendCommand(VirtualKeyCode.RMENU, "Hardware Keyboard Emoticon Pressed", "Right Alt")
    End Sub

    Private Sub SendBack(ByVal sender As Object, ByVal e As EventArgs) Handles btnBack.Click, BackToolStripMenuItem.Click
        SendCommand(VirtualKeyCode.F1, "Back Button Pressed", "F1")
    End Sub

    Private Sub SendStart(ByVal sender As Object, ByVal e As EventArgs) Handles btnStart.Click, StartToolStripMenuItem.Click
        SendCommand(VirtualKeyCode.F2, "Start Button Pressed", "F2")
    End Sub

    Private Sub SendSearch(ByVal sender As Object, ByVal e As EventArgs) Handles btnSearch.Click, SearchToolStripMenuItem.Click
        SendCommand(VirtualKeyCode.F3, "Search Button Pressed", "F3")
    End Sub

#End Region

#Region "Screenshots"

    ''' <summary>
    ''' Takes a screenshot of the emulator screen, adds a skin (if appropriate), sets meta data,
    ''' increments the counter (if appropriate) and saves the file
    ''' </summary>
    Private Sub TakeScreenshot(ByVal sender As Object, ByVal e As EventArgs) Handles btnCapture.Click, TakeAScreenshotToolStripMenuItem.Click, hkScreenshot.Pressed
        'Make sure we can save a file before doing anything (no directory, no dice)!
        If Not My.Computer.FileSystem.DirectoryExists(txtScreenshotDirectory.Text) Then
            MsgBox(String.Format("The Selected Directory cannot be found!{0}{0}{1}", Environment.NewLine, txtScreenshotDirectory.Text), MsgBoxStyle.Exclamation Or MsgBoxStyle.OkOnly, "Imaginary Folder is Imaginary And We are Sad")
            Exit Sub
        End If

        Try
            Dim hWnd As IntPtr = ScreenShot.ProcessHandle(Config.Emulator.ProcessName)
            Dim sSkin As New ScaledSkin(cbScreenshotSkin.SelectedItem, Config.Emulator, My.Settings.LandscapeLeft, hWnd, AddStatsToSkinsToolStripMenuItem.Checked)
            ScreenShot.BringWindowToFront(hWnd)

            'Retrieve the current emulator screen image
            Dim BaseScreenshot As Bitmap = ScreenShot.GetWindow(hWnd, sSkin.Screen)

            Dim Result As Bitmap
            If sSkin.OriginalSkin.IsDummySkin Then
                'Just the screenshot
                Result = BaseScreenshot
            Else
                'Draw the screenshot first, then the skin on top
                Result = New Bitmap(sSkin.Image.Size.Width, sSkin.Image.Size.Height)
                Using g As Graphics = Graphics.FromImage(Result)
                    g.DrawImage(BaseScreenshot, sSkin.ScreenOrigin)
                    g.DrawImage(sSkin.Image, New Point(0, 0))
                End Using
            End If

            'Dynamic save location
            Dim FileName As String = String.Format("{0}.{1}", txtScreenshotFilename.Text.Replace(COUNTERPLACEHOLDER, nudCounter.Value.ToString), cbFormat.SelectedItem.ToString)
            Dim FilePath As String = IIf(txtScreenshotDirectory.Text.EndsWith("\"), txtScreenshotDirectory.Text, txtScreenshotDirectory.Text & "\") & FileName

            'Meta Data
            ImageMeta.SetProperty(Result, ImageMeta.PropertyIDs.Software, "WireBear Awesemulator")
            If chkMetaCopyright.Checked AndAlso txtMetaCopyright.Text.Length > 0 Then ImageMeta.SetProperty(Result, ImageMeta.PropertyIDs.Copyright, txtMetaCopyright.Text)
            If chkMetaAuthor.Checked AndAlso txtMetaAuthor.Text.Length > 0 Then ImageMeta.SetProperty(Result, ImageMeta.PropertyIDs.Artist, txtMetaAuthor.Text)

            'Save the Screenshot
            Result.Save(FilePath, cbFormat.SelectedItem)
            Result.Dispose()

            'Add Screenshot information to StatusStrip
            If txtScreenshotFilename.Text.Contains(COUNTERPLACEHOLDER) Then nudCounter.Value += 1
            lblStatus.Text = "ScreenShot: " & FileName
            LastScreenshotPath = FilePath
            CurrentStatusBarState = StatusBarState.Screenshot
        Catch ex As Exception
            MsgBox(ex.ToString())
        End Try
    End Sub

#End Region

#Region "Overlay"

    ''' <summary>
    ''' Switches the visibility of the overlay and any custom pointer (shows if hidden, hides if visible)
    ''' </summary>
    Private Sub ToggleOverlay(ByVal sender As Object, ByVal e As EventArgs) Handles btnOverlayDisplay.Click, ToggleOverlayToolStripMenuItem.Click, hkOverlay.Pressed
        If myOverlay.Visible OrElse myPointer.Visible Then
            myOverlay.Hide()
            HidePointer()
            If chkSyncCursorWithOverlay.Checked Then AdjustGlobalCursor(True)
        Else
            DisplayOverlay(sender, e)
        End If
    End Sub

    ''' <summary>
    ''' Shows an Overlay skin over the top of the emulator window along with a custom pointer
    ''' Since it recalculates position, it is also good for when the emulator moves
    ''' </summary>
    Private Sub DisplayOverlay(ByVal sender As Object, ByVal e As EventArgs) Handles RefreshToolStripMenuItem.Click
        Try
            Dim hWnd As IntPtr = ScreenShot.ProcessHandle(Config.Emulator.ProcessName)
            ScreenShot.BringWindowToFront(hWnd)

            'Display Overlay
            If Not CType(cbOverlaySkin.SelectedItem, Skin).IsDummySkin Then
                Dim sSkin As New ScaledSkin(cbOverlaySkin.SelectedItem, Config.Emulator, My.Settings.LandscapeLeft, hWnd, AddStatsToSkinsToolStripMenuItem.Checked)

                'Find current emulator position
                Dim wRect As Rectangle = ScreenShot.WindowRectangle(hWnd)
                Dim ActualSceenPoint As New Point(wRect.X + sSkin.Screen.Left, wRect.Y + sSkin.Screen.Top)

                'Figure out relative origin of overlay to make sense for current emulator position
                Dim OverlayOrigin As New Point(ActualSceenPoint.X - sSkin.ScreenOrigin.X, ActualSceenPoint.Y - sSkin.ScreenOrigin.Y)
                myOverlay.SetBackgroundImage(sSkin.Image)
                myOverlay.Show()
                myOverlay.Location = OverlayOrigin
            Else
                myOverlay.Hide()
            End If

            'Show cursor
            Dim ci As CursorInfo = cbOverlayCursor.SelectedItem
            If Not ci.IsDummyCursor Then
                Dim scale As Scaling = CType(cbOverlayCursorScale.SelectedItem, Scaling)
                Dim sCursor As ScaledCursor
                If scale.HeightScale = 0 Then
                    sCursor = New ScaledCursor(ci, Config.Emulator, hWnd)
                Else
                    sCursor = New ScaledCursor(ci, scale.WidthScale, scale.HeightScale)
                End If
                If ci.DetectPressedEvents Then
                    myPointer.SetImages(sCursor.HotSpot, sCursor.Image, sCursor.ImagePress)
                Else
                    myPointer.SetImages(sCursor.HotSpot, sCursor.Image)
                End If
                myPointer.Show()
                myPointer.StartTracking()
                If chkSyncCursorWithOverlay.Checked Then AdjustGlobalCursor(False)
            Else
                HidePointer()
                If chkSyncCursorWithOverlay.Checked Then AdjustGlobalCursor(True)
            End If
        Catch ex As Exception
            MsgBox(ex.ToString())
        End Try
    End Sub

    ''' <summary>
    ''' Turns off cursor tracking and hides the custom pointer
    ''' </summary>
    Private Sub HidePointer()
        myPointer.StopTracking()
        myPointer.Hide()
    End Sub

    ''' <summary>
    ''' Hides or shows the Actual cursor (Affects ALL programs, but gives the nice effect for custom pointer overlays)
    ''' </summary>
    Private Sub AdjustGlobalCursor(Visible As Boolean)
        If GlobalCursorVisible = Visible Then Exit Sub

        GlobalCursorVisible = Visible
        ShowGlobalCursorToolStripMenuItem.Checked = GlobalCursorVisible
        If GlobalCursorVisible Then
            CursorInfo.RestoreCursors()
        Else
            CursorInfo.SetCursor(CursorInfo.CreateCursor(My.Resources.Empty, New Point(0, 0)), CursorInfo.OCR_SYSTEM_CURSORS.OCR_NORMAL)
        End If
    End Sub

    Private Sub ToggleGlobalCursor(sender As Object, e As EventArgs) Handles ShowGlobalCursorToolStripMenuItem.Click, hkCursor.Pressed
        AdjustGlobalCursor(Not GlobalCursorVisible)
    End Sub
#End Region

End Class